// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
using Roslyn.Utilities;

namespace dnSpy.Roslyn.EditorFeatures.Extensions {
	static class ITextSnapshotLineExtensions {
		/// <summary>
		/// Returns the first non-whitespace position on the given line, or null if
		/// the line is empty or contains only whitespace.
		/// </summary>
		public static int? GetFirstNonWhitespacePosition(this ITextSnapshotLine line) {
			Contract.ThrowIfNull(line);

			var text = line.GetText();

			for (var i = 0; i < text.Length; i++) {
				if (!char.IsWhiteSpace(text[i])) {
					return line.Start + i;
				}
			}

			return null;
		}

		/// <summary>
		/// Returns the first non-whitespace position on the given line as an offset
		/// from the start of the line, or null if the line is empty or contains only
		/// whitespace.
		/// </summary>
		public static int? GetFirstNonWhitespaceOffset(this ITextSnapshotLine line) {
			Contract.ThrowIfNull(line);

			var text = line.GetText();

			for (var i = 0; i < text.Length; i++) {
				if (!char.IsWhiteSpace(text[i])) {
					return i;
				}
			}

			return null;
		}

		/// <summary>
		/// Returns the last non-whitespace position on the given line, or null if
		/// the line is empty or contains only whitespace.
		/// </summary>
		public static int? GetLastNonWhitespacePosition(this ITextSnapshotLine line) =>
			line.AsTextLine().GetLastNonWhitespacePosition();

		/// <summary>
		/// Determines whether the specified line is empty or contains whitespace only.
		/// </summary>
		public static bool IsEmptyOrWhitespace(this ITextSnapshotLine line, int startIndex = 0, int endIndex = -1) {
			Contract.ThrowIfNull(line, "line");
			Contract.ThrowIfFalse(startIndex >= 0);

			var text = line.GetText();

			if (endIndex == -1) {
				endIndex = text.Length;
			}

			for (var i = startIndex; i < endIndex; i++) {
				if (!char.IsWhiteSpace(text[i])) {
					return false;
				}
			}

			return true;
		}

		public static ITextSnapshotLine GetPreviousMatchingLine(this ITextSnapshotLine line,
			Func<ITextSnapshotLine, bool> predicate) {
			Contract.ThrowIfNull(line, @"line");
			Contract.ThrowIfNull(predicate, @"tree");

			if (line.LineNumber <= 0) {
				return null;
			}

			var snapshot = line.Snapshot;
			for (var lineNumber = line.LineNumber - 1; lineNumber >= 0; lineNumber--) {
				var currentLine = snapshot.GetLineFromLineNumber(lineNumber);
				if (!predicate(currentLine)) {
					continue;
				}

				return currentLine;
			}

			return null;
		}

		public static int GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(this ITextSnapshotLine line,
			IEditorOptions editorOptions) => line.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(editorOptions.GetTabSize());

		public static int GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(this ITextSnapshotLine line, int tabSize) =>
			line.GetText().GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(tabSize);

		public static int GetColumnFromLineOffset(this ITextSnapshotLine line, int lineOffset, IEditorOptions editorOptions) =>
			line.GetText().GetColumnFromLineOffset(lineOffset, editorOptions.GetTabSize());

		public static int GetLineOffsetFromColumn(this ITextSnapshotLine line, int column, IEditorOptions editorOptions) =>
			line.GetText().GetLineOffsetFromColumn(column, editorOptions.GetTabSize());

		/// <summary>
		/// Checks if the given line at the given snapshot index starts with the provided value.
		/// </summary>
		public static bool StartsWith(this ITextSnapshotLine line, int index, string value, bool ignoreCase) {
			var snapshot = line.Snapshot;
			if (index + value.Length > snapshot.Length)
				return false;

			for (var i = 0; i < value.Length; i++) {
				var snapshotIndex = index + i;
				var actualCharacter = snapshot[snapshotIndex];
				var expectedCharacter = value[i];

				if (ignoreCase) {
					actualCharacter = char.ToLowerInvariant(actualCharacter);
					expectedCharacter = char.ToLowerInvariant(expectedCharacter);
				}

				if (actualCharacter != expectedCharacter)
					return false;
			}

			return true;
		}

		public static bool Contains(this ITextSnapshotLine line, int index, string value, bool ignoreCase) {
			var snapshot = line.Snapshot;
			for (var i = index; i < line.End; i++) {
				if (i + value.Length > snapshot.Length)
					return false;

				if (line.StartsWith(i, value, ignoreCase))
					return true;
			}

			return false;
		}
	}
}
